unit AfterDownload_Actions;

interface

uses
  AfterDownload_Defines,
  Classes;

type
  TActionThread = class(TThread)
  private
    { Private declarations }
    FDoAction: TDoActionFunc;
    aRule: Rule;
    doAction: anAction;
    pdIndex: Integer;
    function DoDMAction(const Cmd, Params: String): String;
    procedure FileMapping(Sender: TObject; const OldFileName, NewFileName: string);
    //function FirstIDInCat(CatID: String): String;
    function Launch: Integer;
  protected
    procedure Execute; override;
  public
    constructor Create(DoActionFunc: TDoActionFunc = nil);
  end;

procedure TATStart(DoActionFunction: TDoActionFunc = nil);
procedure TATResume;
procedure TATFinish;
//
procedure AddDownload(AInfo: String);

implementation

uses
  gnugettext,
  Locales,
  Windows,
  ShellAPI,
  SysUtils,
  Tokens,
  Wizard,
  RegExpr,
  DMSettings,
  AfterDownload_Log,
  JvSHFileOperation;

const
 sStopped = 'stopped';
 sSpeed = 'speed';

var
 CS_Act: TRTLCriticalSection;
 TAT: TActionThread = nil;
 ProcessData: TProcesses = nil;
 FileOps: TJvSHFileOperation = nil;
 Saved: TStrings = nil;

{ Common stuff }

procedure CSCreate;
begin
 InitializeCriticalSection(CS_Act);
end;

procedure CSFree;
begin
 DeleteCriticalSection(CS_Act);
end;

procedure TATStart(DoActionFunction: TDoActionFunc = nil);
begin
 TATFinish;
 CSCreate;
 FileOps:=TJvSHFileOperation.Create(nil);
 ProcessData:=TProcesses.Create(Process);
 TAT:=TActionThread.Create(DoActionFunction);
 FileOps.OnFileMapping:=TAT.FileMapping;
 Saved:=TStringList.Create;
end;

procedure TATResume;
begin
 if Assigned(TAT) then
  begin
   if TAT.Suspended then
     TAT.Resume;
  end;
end;

procedure TATFinish;
begin
 if Assigned(TAT) then
  begin
   TAT.Terminate;
   if not TAT.Suspended then
     TAT.WaitFor;
   FreeAndNil(TAT);
  end;
 if Assigned(ProcessData) then
   FreeAndNil(ProcessData);
 if Assigned(FileOps) then
   FreeAndNil(FileOps);
 if Assigned(Saved) then
   FreeAndNil(Saved);
 CSFree;
end;

procedure AddDownload(AInfo: String);
begin
 EnterCriticalSection(CS_Act);
 with ProcessData.Add do
  begin
   Info:=AInfo;
   RuleID:=ridNotSaved; //means file is not saved yet
   ActionID:=aidNoAction;
  end;
 LeaveCriticalSection(CS_Act);
end;

procedure CreateSemaphore(Filename: String);
 var
  FileHandle: Integer;
begin
 try
  FileHandle := FileCreate(Filename);
 except
  on E: Exception do
   begin
    if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
      Log(cLogError, Format(_('Cannot create semaphore %s: error "%s"'), [Filename, E.Message]));
    FileHandle:=0;
   end;
 end;
 FileClose(FileHandle);
end;

function MAKELANGID(p, s: Integer): Integer;
begin
 Result:=(WORD(s) shl 10) or WORD(p);
end;

function GetSaved(const What: string; LIFO: Boolean = False): String;
 var
   i: Integer;
begin
 if Assigned(Saved) and (What<>'') then
  begin
   if LIFO then
    begin
     Result:='';
     i:=Saved.Count-1;
     while i>=0 do
      begin
       if Saved.Names[i]=What then
        begin
         Result:=Saved.ValueFromIndex[i];
         Saved.Delete(i);
         Break;
        end
       else
         Dec(i);
      end;
    end
   else
    begin
     i:=Saved.IndexOfName(What);
     if i<0 then
       Result:=''
     else
      begin
       Result:=Saved.ValueFromIndex[i];
       Saved.Delete(i);
      end;
    end;
  end;
end;

{
function DeleteFileToRecycle(const sFileName: String; ToRecycle: Boolean = false): Boolean;
var
   fos: TSHFileOpStruct;
begin
 FillChar(fos, SizeOf(fos), 0);
  with fos do
   begin
     wFunc := FO_DELETE;
     StrPCopy(pFrom, sFileName);
     fFlags := FOF_NOCONFIRMATION or FOF_SILENT;
     if ToRecycle then
       fFlags:=fFlags or FOF_ALLOWUNDO;
   end;
 Result := ShFileOperation(fos)=0;
end;
}

{ TActionThread }

constructor TActionThread.Create(DoActionFunc: TDoActionFunc = nil);
begin
 inherited Create(true);
 Priority:=tpLower;
 FDoAction:=DoActionFunc;
 aRule:=nil;
 doAction:=nil;
 pdIndex:=-1;
end;

function TActionThread.DoDMAction(const Cmd, Params: String): String;
begin
 if Assigned(FDoAction) then
   Result:=FDoAction(Cmd, Params);
end;
{
function TActionThread.FirstIDInCat(CatID: String): String;
 var
  i: Integer;
  ids, id, idi: String;
begin
 Result:='';
 if StrToIntDef(CatID, -1)>0 then
  begin
   ids:=DoDMAction('GetDownloadIDsList', '0');
   for i:=1 to WordCount(ids, [' ']) do
    begin
     id:=ExtractWord(i, ids, [' ']);
     idi:=DoDMAction('GetDownloadInfoByID', id);
     if ExtractToken('categoryid', idi)=CatId then
      begin
       Result:=id;
       Break;
      end;
    end;
  end;
end;
}
function TActionThread.Launch: Integer;
 var
  s, P: String;
  emsg: PChar;
begin
 Result:=dDone; //mark launch as done
 //
 if not FileExists(doAction.Destination) then
  begin
   if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
     Log(cLogError, Format(_('File %s is not found, nothing to launch.'), [doAction.Destination]));
   //
   Exit;
  end;
 //
 if (pdIndex>=0) and (pdIndex<ProcessData.Count) then
  begin
   //FF:=ExtractToken('saveto', ProcessData[pdIndex].Info);
   s:=ExtractToken('saveto', ProcessData[pdIndex].Info);
   //if FileExists(FF) then
   if FileExists(s) then
    begin
     {
     FP:=IncludeTrailingPathDelimiter(ExtractFilePath(FF));
     FN:=ExtractFileName(FF);
     FE:=ExtractFileExt(FN);
     if FE='' then
       FNO:=FN
     else
       FNO:=Copy(FF, 1, Pos(FE, FF)-1);
     P:=doAction.Parameter;
     ReplaceEx(P, tknPath, FF);
     ReplaceEx(P, tknFolder, FP);
     ReplaceEx(P, tknName, FN);
     ReplaceEx(P, tknNameOnly, FNO);
     ReplaceEx(P, tknExtension, FE);
     }
     P:=ExpandMacros(s, doAction.Parameter);
     Log(cLogAction, Format(_('Action "%s %s" has parameters "%s".'), [GetActTypeDesc(doAction.ActType), doAction.Destination, P]));
     if doAction.Flag then
      begin
       //fill process info
       ProcessData[pdIndex].SEI_Clear;
       with ProcessData[pdIndex].SEI do
        begin
         cbSize:=SizeOf(TShellExecuteInfo);
         fMask := SEE_MASK_NOCLOSEPROCESS;
         Wnd   := 0;  // forms handle
         lpVerb:= 'open';
         lpFile:= PChar(doAction.Destination);
         lpParameters := PChar(P);
         nShow := SW_SHOWMINNOACTIVE;
        end;
       //
       if ShellExecuteEx(@ProcessData[pdIndex].SEI) then
         Result:=dContinue
       else
        begin
         ProcessData[pdIndex].SEI_Clear;
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
          begin
           emsg:=nil;
           FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM, nil, GetLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), emsg, 0, nil);
           Log(cLogError, Format(_('Cannot launch file %s: %s.'), [doAction.Destination, emsg]));
           LocalFree(Cardinal(emsg));
          end;
        end;
      end
     else
      begin
        case ShellExecute(0, 'open', PChar(doAction.Destination), PChar(P), nil, SW_SHOWMINNOACTIVE) of
         ERROR_BAD_FORMAT:
          begin
           //MessageBox(0, PChar(ra.Path+#13+seCannotStart), PChar(myPluginName), MB_OK or MB_TOPMOST or MB_ICONERROR);
           if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
             Log(cLogError, Format(_('File %s cannot be launched.'), [doAction.Destination]));
          end;
         ERROR_FILE_NOT_FOUND:
          begin
           //MessageBox(0, PChar(ra.Path+#13+seInvalidFile), PChar(myPluginName), MB_OK or MB_TOPMOST or MB_ICONERROR);
           if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
             Log(cLogError, Format(_('File %s is not found.'), [doAction.Destination]));
          end;
         ERROR_PATH_NOT_FOUND:
          begin
           //MessageBox(0, PChar(ra.Path+#13+seInvalidFolder), PChar(myPluginName), MB_OK or MB_TOPMOST or MB_ICONERROR);
           if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
             Log(cLogError, Format(_('Path to file %s is not found.'), [doAction.Destination]));
          end;
         end;
      end;
    end
   else
    begin
     if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
       //Log(cLogError, Format(_('File %s is not found.'), [FF]));
       Log(cLogError, Format(_('File %s is not found.'), [s]));
    end;
  end;
end;

procedure TActionThread.Execute;
 var
  i, t, l: Integer;
  fn, newfn, oldfn, dlist: String;
  ws: WideString;
  AOptions: TJvShFileOptions;
  lRule: Rule;
  re: TRegExpr;
  b: Boolean;
begin
 while not Terminated do
  begin
   i:=0;
   while (not Terminated) and (i<ProcessData.Count) do
    begin
     EnterCriticalSection(CS_Act);
     fn:=ExtractToken('saveto', ProcessData[i].Info);
     case ProcessData[i].RuleID of
      ridDelete: //ready to delete
       begin
        ProcessData.Delete(i);
       end;
      ridNotSaved: //file is not saved yet
       begin
        //look for saved file
        if FileExists(fn) then
         begin
          //ready to search rule;
          ProcessData[i].RuleID:=ridNoRule;
          if IsBitsSet(myRules.Log, [cLogUse, cLogFileSaved]) then
            Log(cLogFile, Format(_('File %s was saved.'), [fn]));
         end;
        Inc(i); //proceed to next
       end;
      ridNoRule: //search matching rule
       begin
        EnterCriticalSection(CS_Rules);
        for t:=0 to myRules.Rules.Count-1 do
         begin
          if myRules.Rules[t].Enabled then
           begin
            if myRules.Rules[t].Matched(ProcessData[i].Info) then
             begin
              //setup process for matched rule
              ProcessData[i].RuleID:=myRules.Rules[t].ID;
              ProcessData[i].ActionID:=aidNoAction;
              //
              if IsBitsSet(myRules.Log, [cLogUse, cLogMatched]) then
                Log(cLogRule, Format(_('File %s is matched by rule %s.'), [fn, myRules.Rules[t].Title]));
              //
              Break; //stop count!
             end;
           end;
         end;
        if ProcessData[i].RuleID=ridNoRule then
         begin
          //no matching rules - mark to delete
          ProcessData[i].RuleID:=ridDelete;
          //
          if IsBitsSet(myRules.Log, [cLogUse, cLogUnmatched]) then
            Log(cLogRule, Format(_('File %s is not matched by any rule.'), [fn]));
         end;
        LeaveCriticalSection(CS_Rules);
        Inc(i); //proceed to next
       end;
      else //have rule, do actions
       begin
        //find rule
        aRule:=Rule(myRules.Rules.FindItemID(ProcessData[i].RuleID));
        if Assigned(aRule) then
         begin
          case ProcessData[i].ActionID of
           aidComplete: //all actions of rule was performed
            begin
             (*
             if aRule.Others then
              begin
               //find next rule
               t:=aRule.Index+1;
               if t<myRules.Rules.Count then
                begin
                 //has following rules
                 ProcessData[i].RuleID:=myRules.Rules[t].ID;
                end
               else
                begin
                 //no more rules - ready to delete
                 ProcessData[i].RuleID:=ridDelete;
                end;
               ProcessData[i].ActionID:=aidNoAction;
              end
             else
              begin
               //processed and ready to delete
               ProcessData[i].RuleID:=ridDelete;
               ProcessData[i].ActionID:=aidNoAction;
              end;
             *)
             //processed and ready to delete
             ProcessData[i].RuleID:=ridDelete;
             ProcessData[i].ActionID:=aidNoAction;
             if aRule.Others then
              begin
               //try to find other MATCHING rule
               lRule:=Rule(myRules.Rules.FindItemID(aRule.ID));
               if Assigned(lRule) then
                 l:=aRule.Index+1
               else //rule was deleted
                 l:=aRule.Index;
               if IsBitsSet(myRules.Log, [cLogUse, cLogMatched]) and (l<myRules.Rules.Count) then
                 Log(cLogRule, Format(_('Searching other matching rule from rule %s...'), [myRules.Rules[l].Title]));
               //
               for t:=l to myRules.Rules.Count-1 do
                begin
                 if myRules.Rules[t].Enabled then
                  begin
                   if myRules.Rules[t].Matched(ProcessData[i].Info) then
                    begin
                     //setup process for matched rule
                     ProcessData[i].RuleID:=myRules.Rules[t].ID;
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogMatched]) then
                       Log(cLogRule, Format(_('File %s is matched by rule %s.'), [fn, myRules.Rules[t].Title]));
                     //
                     Break;
                    end;
                  end;
                end;
               //
               if IsBitsSet(myRules.Log, [cLogUse, cLogMatched]) and (ProcessData[i].RuleID=ridDelete) then
                 Log(cLogRule, _('No other matching rules found.'));
               //
              end;
            end;
           aidNoAction: //no action is active yet
            begin
             //setup for first action
             ProcessData[i].ActionID:=aRule.Actions[0].ID;
            end;
           else //have action, do it
            begin
             doAction:=anAction(aRule.Actions.FindItemID(ProcessData[i].ActionID));
             if Assigned(doAction) then
              begin
               //check for done;
               case ProcessData[i].Done of
                dDone:
                 begin
                  if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                    Log(cLogAction, Format(_('Action "%s %s" is performed with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                  //find next action
                  t:=doAction.Index+1;
                  if t<aRule.Actions.Count then
                   begin
                    //has following actions
                    ProcessData[i].ActionID:=aRule.Actions[t].ID;
                   end
                  else
                   begin
                    //no more actions - rule is complete
                    ProcessData[i].ActionID:=aidComplete;
                   end;
                  ProcessData[i].Done:=dNotStarted;
                 end;
                dContinue:
                 begin
                  if not IsProcessActive(ProcessData[i].SEI) then
                    ProcessData[i].Done:=dDone;
                 end;
                else
                  //do action itself
                  case doAction.ActType of
                   actCopy:
                    begin
                     pdIndex:=i; //save Process index for data changing
                     AOptions := [fofFilesOnly, fofNoConfirmMkDir, fofWantMappingHandle];
                     if myRules.Silent then
                       Include(AOptions, fofSilent);
                     case doAction.Existing of
                      actexRename: Include(AOptions, fofRenameOnCollision);
                      actexOverwrite: Include(AOptions, fofNoConfirmation);
                      end;
                     FileOps.SourceFiles.Clear;
                     FileOps.SourceFiles.Add(fn);
                     newfn:=IncludeTrailingPathDelimiter(doAction.Destination);
                     FileOps.DestFiles.Clear;
                     FileOps.DestFiles.Add(newfn);
                     FileOps.Operation:=foCopy;
                     FileOps.Options:=AOptions;
                     try
                      FileOps.Execute;
                      if doAction.Flag then
                       begin
                        //look for name collision
                        oldfn:=ExtractToken('saveto', ProcessData[i].Info);
                        if oldfn=fn then
                         begin
                          //was no collision - update info
                          ws:=ProcessData[i].Info;
                          ReplaceToken('saveto', newfn+ExtractFileName(oldfn), ws);
                          ProcessData[i].Info:=ws;
                         end;
                       end;
                     except
                      on E: Exception do
                       begin
                        if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                          Log(cLogError, Format(_('Cannot copy file %s to %s: error "%s"'), [fn, newfn, E.Message]));
                       end;
                     end;
                     ProcessData[i].Done:=0;
                    end;
                   actMove:
                    begin
                     pdIndex:=i; //save Process index for data changing
                     AOptions := [fofFilesOnly, fofNoConfirmMkDir, fofWantMappingHandle];
                     if myRules.Silent then
                       Include(AOptions, fofSilent);
                     case doAction.Existing of
                      actexRename: Include(AOptions, fofRenameOnCollision);
                      actexOverwrite: Include(AOptions, fofNoConfirmation);
                      end;
                     FileOps.SourceFiles.Clear;
                     FileOps.SourceFiles.Add(fn);
                     newfn:=IncludeTrailingPathDelimiter(doAction.Destination);
                     FileOps.DestFiles.Clear;
                     FileOps.DestFiles.Add(newfn);
                     FileOps.Operation:=foMove;
                     FileOps.Options:=AOptions;
                     try
                      FileOps.Execute;
                      if doAction.Flag then
                       begin
                        //look for name collision
                        oldfn:=ExtractToken('saveto', ProcessData[i].Info);
                        if oldfn=fn then
                         begin
                          //was no collision - update info
                          ws:=ProcessData[i].Info;
                          ReplaceToken('saveto', newfn+ExtractFileName(oldfn), ws);
                          ProcessData[i].Info:=ws;
                         end;
                       end;
                     except
                      on E: Exception do
                       begin
                        if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                          Log(cLogError, Format(_('Cannot move file %s to %s: error "%s"'), [fn, newfn, E.Message]));
                       end;
                     end;
                     ProcessData[i].Done:=0;
                    end;
                   actLaunch:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     pdIndex:=i;
                     ProcessData[i].Done:=Launch;
                    end;
                   actStart:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       //find first download of category
                       if Assigned(myCatFunction) then
                         ws:=myCatFunction(CatList.Names[IndexOfValue(doAction.Destination, CatList)])
                       else
                        begin
                         ws:='';
                         if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                           Log(cLogError, _('Category handle function is not assigned. Please contact the author.'));
                        end;
                       if ws='' then
                        begin
                         if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                          begin
                           if Assigned(myCatFunction) then
                             ws:=myCatFunction(CatList.Names[IndexOfValue(doAction.Destination, CatList)], true);
                           if ws='' then
                             Log(cLogError, Format(_('Category %s has no downloads, nothing is started.'), [doAction.Destination]))
                           else
                             Log(cLogError, Format(_('Category %s has no paused downloads.'), [doAction.Destination]));
                          end;
                        end
                       else
                         DoDMAction('StartDownloads', ws);
                      end
                     else
                       DoDMAction('StartNode', CatList.Names[IndexOfValue(doAction.Destination, CatList)]);
                     ProcessData[i].Done:=0;
                    end;
                   actFlagCreate:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     CreateSemaphore(doAction.Destination);
                     ProcessData[i].Done:=0;
                    end;
                   actFlagDelete, actFileDeletePerm, actDownloadedDelete:
                    begin
                     //actDownloadedDelete specific
                     if doAction.ActType=actDownloadedDelete then
                       doAction.Destination:=fn;
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     AOptions := [fofFilesOnly, fofNoConfirmMkDir, fofWantMappingHandle];
                     if myRules.Silent then
                       Include(AOptions, fofSilent);
                     //actFlagDelete, actDownloadedDelete specific
                     case doAction.ActType of
                      actFlagDelete: Include(AOptions, fofAllowUndo);
                      actDownloadedDelete: if not doAction.Flag then Include(AOptions, fofAllowUndo);
                      end;
                     //
                     case doAction.Existing of
                      actexRename: Include(AOptions, fofRenameOnCollision);
                      actexOverwrite: Include(AOptions, fofNoConfirmation);
                      end;
                     FileOps.SourceFiles.Clear;
                     FileOps.SourceFiles.Add(doAction.Destination);
                     FileOps.Operation:=foDelete;
                     FileOps.Options:=AOptions;
                     try
                      FileOps.Execute;
                     except
                      on E: Exception do
                       begin
                        if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                          Log(cLogError, Format(_('Cannot delete file %s: error "%s"'), [doAction.Destination, E.Message]));
                       end;
                     end;
                     ProcessData[i].Done:=0;
                    end;
                   actRename:
                    begin
                     pdIndex:=i; //save Process index for data changing
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     AOptions := [fofFilesOnly, fofNoConfirmMkDir, fofWantMappingHandle];
                     if myRules.Silent then
                       Include(AOptions, fofSilent);
                     case doAction.Existing of
                      actexRename: Include(AOptions, fofRenameOnCollision);
                      actexOverwrite: Include(AOptions, fofNoConfirmation);
                      end;
                     FileOps.SourceFiles.Clear;
                     FileOps.SourceFiles.Add(fn);
                     re:=TRegExpr.Create;
                     re.Expression:=doAction.Destination;
                     try
                      b:=re.Exec(fn);
                      if b then
                        doAction.Destination:=re.Match[0]
                      else
                        b:=IsValidFilename(ExtractFileName(doAction.Destination));
                      if b then
                       begin
                        newfn:=IncludeTrailingPathDelimiter(ExtractFilePath(fn))+doAction.Destination;
                        FileOps.DestFiles.Clear;
                        FileOps.DestFiles.Add(newfn);
                        FileOps.Operation:=foRename;
                        FileOps.Options:=AOptions;
                        try
                         FileOps.Execute;
                         if doAction.Flag then
                          begin
                           //look for name collision
                           oldfn:=ExtractToken('saveto', ProcessData[i].Info);
                           if oldfn=fn then
                            begin
                             //was no collision - update info
                             ws:=ProcessData[i].Info;
                             ReplaceToken('saveto', newfn, ws);
                             ProcessData[i].Info:=ws;
                            end;
                          end;
                        except
                         on E: Exception do
                          begin
                           if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                             Log(cLogError, Format(_('Cannot rename file %s to %s: error "%s"'), [fn, doAction.Destination, E.Message]));
                          end;
                        end;
                       end
                      else
                       begin
                        if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                          Log(cLogError, Format(_('Cannot rename file %s to %s: regexp does not match.'), [fn, doAction.Destination]));
                       end;
                     finally
                      FreeAndNil(re);
                     end;
                     ProcessData[i].Done:=0;
                    end;
                   actRuleEnable:
                    begin
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     EnterCriticalSection(CS_Rules);
                     t:=myRules.Rules.IndexOfTitle(doAction.Destination);
                     if t<0 then
                      begin
                       if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                         Log(cLogAction, Format(_('Cannot find rule "%s".'), [doAction.Destination]));
                      end
                     else
                      begin
                       if myRules.Rules[t].Enabled then
                        begin
                         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                           Log(cLogAction, Format(_('Rule "%s" is enabled already.'), [doAction.Destination]));
                        end
                       else
                        begin
                         myRules.Rules[t].Enabled:=True;
                         //
                         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                           Log(cLogAction, Format(_('Rule "%s" was enabled.'), [doAction.Destination]));
                        end;
                      end;
                     LeaveCriticalSection(CS_Rules);
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actRuleDisable:
                    begin
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     EnterCriticalSection(CS_Rules);
                     t:=myRules.Rules.IndexOfTitle(doAction.Destination);
                     if t<0 then
                      begin
                       if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                         Log(cLogAction, Format(_('Cannot find rule "%s".'), [doAction.Destination]));
                      end
                     else
                      begin
                       if myRules.Rules[t].Enabled then
                        begin
                         myRules.Rules[t].Enabled:=False;
                         //
                         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                           Log(cLogAction, Format(_('Rule "%s" was disabled.'), [doAction.Destination]));
                        end
                       else
                        begin
                         if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                           Log(cLogAction, Format(_('Rule "%s" is disabled already.'), [doAction.Destination]));
                        end;
                      end;
                     LeaveCriticalSection(CS_Rules);
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actRuleDelete:
                    begin
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     EnterCriticalSection(CS_Rules);
                     t:=myRules.Rules.IndexOfTitle(doAction.Destination);
                     if t<0 then
                      begin
                       if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                         Log(cLogAction, Format(_('Cannot find rule "%s".'), [doAction.Destination]));
                      end
                     else
                      begin
                       myRules.Rules.Delete(t);
                       //
                       if IsBitsSet(myRules.Log, [cLogUse, cLogChanged]) then
                         Log(cLogAction, Format(_('Rule "%s" was deleted.'), [doAction.Destination]));
                      end;
                     LeaveCriticalSection(CS_Rules);
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actStartAll:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     DoDMAction('StartAll', '');
                     ProcessData[i].Done:=0;
                    end;
                   actStopAll:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       dlist:=DoDMAction('GetDownloadIDsList', '3');
                       dlist:=dlist+DoDMAction('GetDownloadIDsList', '6');
                       //
                       Saved.Add(sStopped+'='+dlist);
                      end;
                     DoDMAction('StopAll', '');
                     ProcessData[i].Done:=0;
                    end;
                   actStartStopped:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     dlist:=GetSaved(sStopped, myRules.SavedReturn=cSavedLIFO);
                     if dlist='' then
                      begin
                       if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                         Log(cLogAction, Format(_('No saved "to start" downloads found.'), [dlist]));
                      end
                     else
                      begin
                       DoDMAction('StartDownloads', dlist);
                       //
                       if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                         Log(cLogAction, Format(_('Downloads was started:%s.'), [dlist]));
                      end;
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedMax:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       t:=DMSpeedMode;
                       //
                       Saved.Add(sSpeed+'='+IntToStr(t));
                      end;
                     DoDMAction('ChangeSpeed', IntToStr(smMax));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [_(actSpeedMaxDesc)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedMed:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       t:=DMSpeedMode;
                       //
                       Saved.Add(sSpeed+'='+IntToStr(t));
                      end;
                     DoDMAction('ChangeSpeed', IntToStr(smNormal));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [_(actSpeedMedDesc)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedMin:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       t:=DMSpeedMode;
                       //
                       Saved.Add(sSpeed+'='+IntToStr(t));
                      end;
                     DoDMAction('ChangeSpeed', IntToStr(smLow));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [_(actSpeedMinDesc)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedAuto:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       t:=DMSpeedMode;
                       //
                       Saved.Add(sSpeed+'='+IntToStr(t));
                      end;
                     DoDMAction('ChangeSpeed', IntToStr(smAuto));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [_(actSpeedAutoDesc)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedAdj:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     if doAction.Flag then
                      begin
                       t:=DMSpeedMode;
                       //
                       Saved.Add(sSpeed+'='+IntToStr(t));
                      end;
                     DoDMAction('ChangeSpeed', IntToStr(smAdjust));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [_(actSpeedAdjDesc)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actSpeedLast:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     dlist:=GetSaved(sSpeed, myRules.SavedReturn=cSavedLIFO);
                     t:=StrToIntDef(dlist, smMax);
                     if not (t in [smMax..smAdjust]) then
                       t:=smMax;
                     DoDMAction('ChangeSpeed', IntToStr(t));
                     //
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Speed was changed to %s.'), [GetActTypeSpeedDesc(t)]));
                     //
                     ProcessData[i].Done:=0;
                    end;
                   actStopCategory:
                    begin
                     if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
                       Log(cLogAction, Format(_('Action "%s %s" is performing with file %s.'), [GetActTypeDesc(doAction.ActType), doAction.Destination, fn]));
                     //
                     DoDMAction('StopNode', CatList.Names[IndexOfValue(doAction.Destination, CatList)]);
                     ProcessData[i].Done:=0;
                    end;
                   end;
               end;
              end
             else
              begin
               //action was deleted - start actions anew
               ProcessData[i].ActionID:=aidNoAction;
               //
               if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
                 Log(cLogError, Format(_('Action was deleted in rule %s when processing file %s - actions will redo.'), [aRule.Title, fn]));
              end;
            end;
           end;
         end
        else
         begin
          //rule was deleted - reset rule search
          ProcessData[i].RuleID:=ridNoRule;
          ProcessData[i].ActionID:=aidNoAction;
          //
          if IsBitsSet(myRules.Log, [cLogUse, cLogActionErrors]) then
            Log(cLogError, Format(_('Rule was deleted when matched file %s - rule search is reset.'), [fn]));
         end;
        Inc(i); //proceed to next
       end;
      end;
     LeaveCriticalSection(CS_Act);
     Sleep(100); //take a rest ;)
    end;
   if ProcessData.Count=0 then
     Suspend; 
  end;
end;

procedure TActionThread.FileMapping(Sender: TObject; const OldFileName, NewFileName: string);
 var
  s: WideString;
begin
 if Assigned(aRule) and Assigned(doAction) then
  begin
   if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
     Log(cLogAction, Format(_('File "%s" was renamed to "%s" while performing action "%s %s" in rule %s.'), [OldFileName, NewFileName, GetActTypeDesc(doAction.ActType), doAction.Destination, aRule.Title]));
   //
   if doAction.ActType in [actCopy, actMove, actRename] then
    begin
     if doAction.Flag and (pdIndex>=0) then
      begin
       //update file info on collision
       if pdIndex<ProcessData.Count then
        begin
         s:=ProcessData[pdIndex].Info;
         ReplaceToken('saveto', NewFileName, s);
         ProcessData[pdIndex].Info:=s;
         //
         if IsBitsSet(myRules.Log, [cLogUse, cLogActions]) then
           Log(cLogAction, _('Download info is updated.'));
        end;
      end;
    end;
  end;
end;

end.
